
if ((!navigator.service) || (navigator.service && !navigator.service.contacts)) {

	var serviceContacts = function(){	

		// CONTACTPROPERTIES interface
		var ContactProperties = function(properties){

			var nameVar = (dappr.isString(properties.name) ? properties.name : null);

			var nicknamesVar = [];
			if(dappr.isArray(properties.nicknames))
				for(var i in properties.nicknames) (dappr.isString(properties.nicknames[i]) ? nicknamesVar.push(properties.nicknames[i]) : false);
				
			var phonesVar = [];
			if(dappr.isArray(properties.phones))
				for(var i in properties.phones) {
					//if(dappr.isArray(properties.phones[i]))
						var phoneField = new ContactField(properties.phones[i]);
						// If the value is set then say it is a valid phone
						if(phoneField != null && phoneField.value != null) phonesVar.push(phoneField);
				}
			
			var emailsVar = [];
			if(dappr.isArray(properties.emails))
				for(var i in properties.emails) {
					//if(dappr.isArray(properties.emails[i]))
						var emailField = new ContactField(properties.emails[i]);
						// If the value is set then say it is a valid email address
						if(emailField != null && emailField.value != null) emailsVar.push(emailField);
				}
				
			var addressesVar = [];
			if(dappr.isArray(properties.addresses))
				for(var i in properties.addresses) {
					if(dappr.isArray(properties.addresses[i]))
						var addressField = new ContactAddress(properties.addresses[i]);
						// If any address fields are set then say it's a valid address:
						var validAddress = false;
						for(var j in addressField) {
							if (addressField[j] != null) {
								validAddress = true;
								break;
							}
						}
						if(validAddress) addressesVar.push(addressField);
				}
				
			var imppsVar = [];
			if(dappr.isArray(properties.impps))
				for(var i in properties.impps) {
					if(dappr.isArray(properties.impps[i]))
						var imppField = new ContactField(properties.impps[i]);
						// If the value is set then say it is a valid impp
						if(imppField.value != null) imppsVar.push(imppField);
				}	
				
			var serviceIdVar = (dappr.isString(properties.serviceId) ? properties.serviceId : null);	
				
			var categoriesVar = [];
			if(dappr.isArray(properties.categories))
				for(var i in properties.categories) (dappr.isString(properties.categories[i]) ? categoriesVar.push(properties.categories[i]) : false);			


			return {
				name: nameVar,
				nicknames: nicknamesVar,
				phones: phonesVar,
				emails: emailsVar,
				addresses: addressesVar,
				impps: imppsVar,
				serviceId: serviceIdVar,
				categories: categoriesVar
			};
		};
		
		// CONTACTFIELD interface
		var ContactField = function(properties){
			var typesVal = (dappr.isArray(properties.types) ? properties.types : []);
			var valueVal = (dappr.isString(properties.value) ? properties.value : null);
			
			return {
				types: typesVal,
				value: valueVal
			}
		};
		
		// CONTACTADDRESS interface
		var ContactAddress = function(properties) {
			var countryVal = (dappr.isString(properties.country) ? properties.country : null);
			var regionVal = (dappr.isString(properties.region) ? properties.region : null);
			var countyVal = (dappr.isString(properties.county) ? properties.county : null);
			var cityVal = (dappr.isString(properties.city) ? properties.city : null);
			var streetVal = (dappr.isString(properties.street) ? properties.street : null);
			var streetNumberVal = (dappr.isString(properties.streetNumber) ? properties.streetNumber : null);
			var premisesVal = (dappr.isString(properties.premises) ? properties.premises : null);
			var additionalInformationVal = (dappr.isString(properties.additionalInformation) ? properties.additionalInformation : null);
			var postalCodeVal = (dappr.isString(properties.postalCode) ? properties.postalCode : null);
			
			return {
				country: countryVal,
				region: regionVal,
				county: countyVal,
				city: cityVal,
				street: streetVal,
				streetNumber: streetNumberVal,
				premises: premisesVal,
				additionalInformation: additionalInformationVal,
				postalCode: postalCodeVal
			}
			
		};
		
		// CONTACT interface
		var Contact = function(properties){
			
			// create a new associated ContactProperties object
			var contactProperties = new ContactProperties(properties);
			
			var contactId = (dappr.isString(properties.id) ? properties.id : createUUID());
			
			function UUIDPart() {
			    return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
			}
			
			function createUUID() {
			    return (UUIDPart()+UUIDPart()+"-"+UUIDPart()+"-"+UUIDPart()+"-"+UUIDPart()+"-"+UUIDPart()+UUIDPart()+UUIDPart());
			}

			var doClone = function(obj) {
			    if (typeof(obj)!=='object'||obj==null) return obj;
			 
			    var c = obj instanceof Array ? [] : {};
			 
			    for (var i in obj) {
			        var prop = obj[i];
			 
			        if (typeof prop == 'object') {
			           if (prop instanceof Array) {
			               c[i] = [];
			 
			               for (var j = 0; j < prop.length; j++) {
			                   if (typeof prop[j] != 'object') {
			                       c[i].push(prop[j]);
			                   } else {
			                       c[i].push(doClone(prop[j]));
			                   }
			               }
			           } else {
			               c[i] = doClone(prop);
			           }
			        } else {
			           c[i] = prop;
			        }
			    }
			 
			    return c;
			};
			
			var doSave = function(successCB, errorCB) {
				
				//if(dappr.isFunction(successCB) == false) return;

				// Get the current Contact data object as a JSON string for submission to service
				var jsonContact = null;
				if ( typeof JSON === "object" && JSON.stringify ) {
		            jsonContact = JSON.stringify( contactProperties );
		        }
				
				var request = {
					feature: 'contacts.io.write',
					params: {
						api: 'contacts',
						method: 'saveContact'
					},
					onSuccess: onSuccessDataFilter,
					data: jsonContact, // POST data (JSON formatted)
					timeout: 60
				};
				
				if(errorCB != undefined && dappr.isFunction(errorCB) != false)
					request.onError = errorCB;
				
				var requestId = dappr.request.add(request);
				
				/*
				 * Callback for the resulting data from the function above for creating an firing original callback 
				 */
				function onSuccessDataFilter(data) {
					data.id = contactId; // make sure the existing id is maintained
					
					var contactObj = new Contact(data);
					
					// fire the original success callback function
					successCB.call(successCB, contactObj);					
				};
				
				// return a PendingOp object from an async method:
				return (new PendingOp(requestId));
			};
			
			var doRemove = function(successCB, errorCB) {
				
				//if(dappr.isFunction(successCB) == false) return;
				
				// Get the current Contact data object as a JSON string for submission to service
				var jsonContact = null;
				if ( typeof JSON === "object" && JSON.stringify ) {
		            jsonContact = JSON.stringify( contactProperties );
		        }
				
				var request = {
					feature: 'contacts.io.write',
					params: {
						api: 'contacts',
						method: 'removeContact'
					},
					onSuccess: onSuccessDataFilter,
					data: jsonContact,
					timeout: 60
				};
				
				if(errorCB != undefined && dappr.isFunction(errorCB) != false)
					request.onError = errorCB;
				
				var requestId = dappr.request.add(request);
				
				/*
				 * Callback for the resulting data from the function above for creating an firing original callback 
				 */
				function onSuccessDataFilter(data) {
					delete data.id; // if an id exists, delete it

					var contactObj = new Contact(data);
					
					// fire the original success callback function
					successCB.call(successCB, contactObj);					
				};
				
				// return a PendingOp object from an async method:
				return (new PendingOp(requestId));
			};

			// return public methods + extension objects
			return dappr.extend({
				
				// BEST WAY TO CREATE A READONLY OBJECTS IN JAVASCRIPT - 3 methods (?)
				//id: __defineGetter__("readOnlyProperty", function() { return contactId; }),
				//id getter: function a() { return contactId; },
				get id() {
					return contactId;
				},
				
				// How to make read-only?
				clone: function() {
					var o = doClone(this);
					o.id = createUUID();
					return o;
				},
					
				// How to make read-only?		
				save: function(successCB, errorCB) {
					return doSave(successCB, errorCB);
				},
				
				// How to make readonly?
				remove: function(successCB, errorCB) { 
					return doRemove(successCB, errorCB);
				}
				
			}, contactProperties);
			
		};
		
		// PENDINGOP Interface
		var PendingOp = function(requestId) {
			
			var id = requestId;
			
			function cancel$() {
				dappr.request.remove(requestId);
			}
			
			return {
				cancel: function() {
					cancel$();
				}
			}
		};

		var findContacts = function(filter, successCB, errorCB, opt) {

					var request = {
						feature: 'contacts.io.read',
						params: {
							api: 'contacts',
							method: 'getContacts',
							properties: {
								filter: filter,
								options: opt
							}
						},
						onSuccess: onSuccessDataFilter,
						data: null,
						timeout: 60 // This is for the actual resulting request to the Dappr API, 
									// not the Authorisation process timeout (?? does this stay here??).
					}
					
					if(errorCB != null) 
						request.onError = errorCB;
					
					// Do the request
					var requestId = dappr.request.add(request);
					
					/*
					 * Callback for the resulting data from the function above for creating an firing original callback 
					 */
					function onSuccessDataFilter(json) {
						var contactObjs = [];
						for (var i in json.data) {
							contactObjs.push(new Contact(json.data[i]));
						}
						
						// fire the original success callback function
						successCB.call(successCB, contactObjs);					
					};					
					
					// return a PendingOp object from an async method:
					return (new PendingOp(requestId));
		};
			
		// API methods (public)
		return {

			// How to make this READ-ONLY? (so developers cannot overwrite accidentally or otherwise?)
			 
			/*
			 * Create a new Contact object: http://dev.w3.org/2009/dap/contacts/Overview.html#widl-Contacts-create
			 */
			create: function(attributes) {
				return (new Contact(attributes));
			},
			
			/*
			 * find a set of contacts based on the provided filter and options {{Asynchronous API request}}
			 */
			find: function(filter, successCB, errorCB, opt) {

// The following code is disabled because all dappr.toXXX methods are returning [xpconnect wrapped native prototype]....???
//e.g. console.log(toString.call(filter));
/*
					// Check for required parameters:
					if(dappr.isObject(filter) == false 
						|| dappr.isFunction(successCB) == false
							|| (errorCB == undefined && opt == undefined)) return;
					
					// ErrorCB is optional and if it isn't a function it should be assumed that errorCB is the opt parameter (shifted one place forward)
					if(opt == undefined && dappr.isFunction(errorCB) == false) {
						opt = errorCB;
						errorCB = null;
					}		
*/		
				return findContacts(filter, successCB, errorCB, opt);
			}		
		
		};
	}();

	// Add Contacts API to dappr object...
	serviceObj.contacts = serviceContacts;
	
};
